
package rcpsp;

import data.Data;
import data.Job;
import ilog.concert.IloException;
import ilog.concert.IloIntVar;
import ilog.concert.IloLinearIntExpr;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.concert.IloObjective;
import ilog.cplex.IloCplex;
import java.util.ArrayList;

// Model with ressource flows
public class ModelFlow extends Model{
    
    public ModelFlow(IloCplex cplex, Data data){
        super(cplex, data);
    }
    
    //Variables de décisions
    private IloIntVar[] S; 
    private IloIntVar[][] x;
    private IloNumVar[][][] f;
    private IloIntVar[][] y;

    @Override
    public void createModel() throws IloException {
        
        //Big M
        int M=data.getHorizon();
        
        //D'abord, pour simplifier le problème, on calcule une borne max sur S et une sur f:
        int sMax=data.getHorizon();
        double fMax=0.0;
        
        for (int i = 0; i < data.getAvailableRess().size(); i++) {
            fMax = Math.max(fMax, data.getAvailableRess().get(i));
        }
        
        //Variables de décisions
        S = cplex.intVarArray(data.getNbJobs()+2, 0, sMax);
        x = new IloIntVar[data.getNbJobs()+2][data.getNbJobs()+2];
        f = new IloNumVar[data.getNbJobs()+2][data.getNbJobs()+2][data.getNbRess()];
        y = new IloIntVar[data.getNbJobs()+2][data.getNbJobs()+2];
        
        //Initialisation des variables
        for (int i = 0; i < data.getNbJobs()+2; i++) {
            
            x[i] = cplex.boolVarArray(data.getNbJobs()+2);
            y[i] = cplex.boolVarArray(data.getNbJobs()+2);
            
            for (int j = 0; j < data.getNbJobs()+2; j++) {
                
                f[i][j]=cplex.numVarArray(data.getNbJobs()+2, 0, fMax);
            
            }
        }
        
        //Objectif
        IloObjective obj = cplex.minimize(S[data.getNbJobs()+1]);
        cplex.add(obj);
        
        //Contrainte ajoutée par mauvaise définition du modèle (x[i][i]=0 pour tout i)
        for (int i = 0; i < data.getNbJobs()+2; i++) {
            cplex.addEq(x[i][i], 0);
        }
        
        //Contraintes (dans l'ordre du modèle notées cti pour la ième contrainte)
        for (int i=0; i<data.getNbJobs()+2;i++) {
            for (Job j : data.getJobs().get(i).getSuccessors()) {
                cplex.addEq(x[i][data.getJobs().indexOf(j)], 1);
            }
        }
        
        for (int i = 0; i < data.getNbJobs()+2; i++) {
            for (int j = i+1; j < data.getNbJobs()+2; j++) {
                cplex.addLe(cplex.sum(x[i][j], x[j][i]),1);
            }
        }
        
        for (int i = 0; i < data.getNbJobs()+2; i++) {
            for (int j = 0; j < data.getNbJobs()+2; j++) {
                for (int k = 0; k < data.getNbJobs()+2; k++) {
                    IloLinearIntExpr expr = cplex.linearIntExpr();
                    expr.addTerm(1, x[i][k]);
                    expr.addTerm(-1, x[i][j]);
                    expr.addTerm(-1, x[j][k]);
                    cplex.addGe(expr, -1);
                }
            }
        }
        
        for (int i = 0; i < data.getNbJobs()+2; i++) {
            for (int j = 0; j < data.getNbJobs()+2; j++) {
                IloLinearIntExpr expr = cplex.linearIntExpr();
                expr.addTerm(1, S[j]);
                expr.addTerm(-1, S[i]);
                expr.addTerm(-data.getJobs().get(i).getProcessTime(), x[i][j]);
                expr.addTerm(-M, x[i][j]);
                cplex.addGe(expr, -M);
            }
        }
        
        
        for (int k = 0; k < data.getNbRess(); k++) {
            for (int i = 1; i < data.getNbJobs()+2; i++) {
                IloLinearNumExpr sum1 = cplex.linearNumExpr();
                for (int j = 0; j < data.getNbJobs()+2; j++) {
                    sum1.addTerm(1.0, f[i][j][k]);
                }
                cplex.addEq(sum1, data.getJobs().get(i).getAmountRequestRess().get(k));
            }
        }
        
        for (int k = 0; k < data.getNbRess(); k++) {
            for (int i = 0; i < data.getNbJobs()+1; i++) {
                IloLinearNumExpr sum1 = cplex.linearNumExpr();
                for (int j = 0; j < data.getNbJobs()+2; j++) {
                    sum1.addTerm(1.0, f[j][i][k]);
                }
                cplex.addEq(sum1, data.getJobs().get(i).getAmountRequestRess().get(k));
            }
        }
        
        for (int k = 0; k < data.getNbRess(); k++) {
            for (int i = 1; i < data.getNbJobs()+2; i++) {
                for (int j = 0; j < data.getNbJobs()+1; j++) {
                    double min = Math.min(data.getJobs().get(i).getAmountRequestRess().get(k),
                                          data.getJobs().get(j).getAmountRequestRess().get(k));
                    cplex.addLe(f[i][j][k], cplex.prod(min, x[i][j]));
                }
            }
        }
        
        for (int k = 0; k < data.getNbRess(); k++) {
            for (int i = 0; i < data.getNbJobs()+2; i++) {
                for (int j = 0; j < data.getNbJobs()+2; j++) {
                    cplex.addGe(f[i][j][k], 0.0);
                }
                cplex.addEq(f[i][i][k],0.0);
            }
        }
        
        
        
        //Constraints added to preserve maximal admissible consumption of ressources.
        // (not present in lesson pdf but have to be)
        for (int k = 0; k < data.getNbRess(); k++) {
                IloLinearNumExpr sum1 = cplex.linearNumExpr();
                for (int i = 1; i < data.getNbJobs()+2; i++) {
                        sum1.addTerm(1.0, f[0][i][k]);
                }
                cplex.addEq(sum1, data.getAvailableRess().get(k));
        }
        
        for (int k = 0; k < data.getNbRess(); k++) {
                IloLinearNumExpr sum1 = cplex.linearNumExpr();
                for (int i = 0; i < data.getNbJobs()+1; i++) {
                        sum1.addTerm(1.0, f[i][data.getNbJobs()+1][k]);
                }
                cplex.addEq(sum1, data.getAvailableRess().get(k));
        } 
    }

    public IloNumVar[] getS() {
        return S;
    }

    @Override
    public long solve() throws IloException {
        
        long timeToSolve = System.nanoTime();;
        ArrayList<Integer> startsJobs = new ArrayList<>();
        
        if(!cplex.solve()) throw new IloException("No optimal Solution found");
        
        for (int i = 0; i < data.getNbJobs()+2; i++) {
            startsJobs.add((int) Math.round(cplex.getValue(S[i])));
        }
        
        timeToSolve = (System.nanoTime() - timeToSolve) / 1000000;
        
        result = new Solution(startsJobs, (int)Math.round(cplex.getObjValue()), timeToSolve);
        
        return timeToSolve;
    }

    @Override
    public Solution getResult() {
        return result;
    }
}
